package com.wh.sftp;

import com.jcraft.jsch.*;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import org.apache.log4j.Logger;
import org.apache.poi.util.IOUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

public class SFTPUtil {

    private ThreadLocal<Scope> threadLocal = new ThreadLocal<>();

    private ChannelSftp sftp;

    private Session session;
    /**
     * FTP 登录用户名
     */
    private String username;
    /**
     * FTP 登录密码
     */
    private String password;
    /**
     * 私钥
     */
    private String privateKey;
    /**
     * FTP 服务器地址IP地址
     */
    private String host;
    /**
     * FTP 端口
     */
    private int port;

    private String hostDir;

    private static Logger log = Logger.getLogger(SFTPUtil.class);

    /**
     * 构造基于密码认证的sftp对象
     *
     * @param username
     * @param password
     * @param host
     * @param port
     */
    public SFTPUtil(String username, String password, String host, int port, String hostDir) {
        this.username = username;
        this.password = password;
        this.host = host;
        this.port = port;
        this.hostDir = hostDir;
    }

    public SFTPUtil() {
//        String username = RedisUtils.getDictValue("SFTP_MEMBER", "SFTP_NAME");
//        String password = RedisUtils.getDictValue("SFTP_MEMBER", "SFTP_PASSWORD");
//        String host = RedisUtils.getDictValue("SFTP_MEMBER", "SFTP_HOST");
//        int port = Integer.parseInt(RedisUtils.getDictValue("SFTP_MEMBER", "SFTP_PORT"));
//        String hostDir = RedisUtils.getDictValue("SFTP_MEMBER", "SFTP_PATH");
//        String username = "sftp";
//        String password = "2020@newImage";
//        String host = "49.232.18.205";
//        int port = 22;
//        String hostDir = "/upload";
        this.username = "ergo_life_dev";
        this.password = "ergo_life_dev";
        this.host = "49.232.76.227";
        this.port = 22;
        this.hostDir = "/upload";
    }

    /**
     * 连接sftp服务器
     *
     * @throws Exception
     */
    public void login() {
        try {
            JSch jsch = new JSch();
            if (privateKey != null) {
                jsch.addIdentity(privateKey);// 设置私钥  
                log.info("sftp connect,path of private key file：{}");
            }
            log.info("sftp connect by host:{" + host + "} username:{" + username + "}");


            session = jsch.getSession(username, host, port);
            log.info("Session is build");
            if (password != null) {
                session.setPassword(password);
            }
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");

            session.setConfig(config);
            log.info(session.toString());
            session.connect();
            log.info("Session is connected");

            Channel channel = session.openChannel("sftp");
            channel.connect();
            log.info("channel is connected");

            sftp = (ChannelSftp) channel;
            log.info(String.format("sftp server host:[%s] port:[%s] is connect successfull", host, port));
        } catch (JSchException e) {
            e.printStackTrace();
            log.error("SFTP连接失败", e);
        }
    }

    /**
     * 关闭连接 server
     */
    public void logout() {
        if (sftp != null) {
            if (sftp.isConnected()) {
                sftp.disconnect();
                log.info("sftp is closed already");
            }
        }
        if (session != null) {
            if (session.isConnected()) {
                session.disconnect();
                log.info("sshSession is closed already");
            }
        }
    }

    public void cd(String directory) throws SftpException {
        sftp.cd(directory);
    }

    public Vector ls(String path) throws SftpException {
        return sftp.ls(path);
    }

    public String pwd() throws Exception {
        String pwd = sftp.pwd();
        return pwd;
    }

    /**
     * 将输入流的数据上传到sftp作为文件
     *
     * @param directory    上传到该目录
     * @param sftpFileName sftp端文件名
     * @param input        输入流
     * @throws SftpException
     * @throws Exception
     */
    public void upload(String directory, String sftpFileName, InputStream input) throws SftpException {
        try {
            sftp.cd(directory);
        } catch (SftpException e) {
            log.warn("directory is not exist");
            mkdirDirs(directory);
            sftp.cd(directory);
        }
        sftp.put(input, sftpFileName);
        log.info("file:{" + sftpFileName + "} is upload successful");
    }

    /**
     * 上传单个文件
     *
     * @param directory  上传到sftp目录
     * @param uploadFile 要上传的文件,包括路径
     * @throws FileNotFoundException
     * @throws SftpException
     * @throws Exception
     */
    public void upload(String directory, String uploadFile) throws FileNotFoundException, SftpException {
        File file = new File(uploadFile);
        upload(directory, file.getName(), new FileInputStream(file));
    }

    /**
     * 将byte[]上传到sftp，作为文件。注意:从String生成byte[]是，要指定字符集。
     *
     * @param directory    上传到sftp目录
     * @param sftpFileName 文件在sftp端的命名
     * @param byteArr      要上传的字节数组
     * @throws SftpException
     * @throws Exception
     */
    public void upload(String directory, String sftpFileName, byte[] byteArr) throws SftpException {
        upload(directory, sftpFileName, new ByteArrayInputStream(byteArr));
    }

    /**
     * 将字符串按照指定的字符编码上传到sftp
     *
     * @param directory    上传到sftp目录
     * @param sftpFileName 文件在sftp端的命名
     * @param dataStr      待上传的数据
     * @param charsetName  sftp上的文件，按该字符编码保存
     * @throws UnsupportedEncodingException
     * @throws SftpException
     * @throws Exception
     */
    public void upload(String directory, String sftpFileName, String dataStr, String charsetName) throws UnsupportedEncodingException, SftpException {
        upload(directory, sftpFileName, new ByteArrayInputStream(dataStr.getBytes(charsetName)));
    }


    /**
     * 下载文件
     *
     * @param directory    下载目录
     * @param downloadFile 下载的文件
     * @param saveFile     存在本地的路径
     * @throws SftpException
     * @throws FileNotFoundException
     * @throws Exception
     */
    public void download(String directory, String downloadFile, String saveFile) throws SftpException, FileNotFoundException {
        if (directory != null && !"".equals(directory)) {
            log.info("directory:" + directory);
            sftp.cd(directory);
        }
        File file = new File(saveFile + downloadFile);
        log.info("downloadFile" + downloadFile);
        sftp.get(downloadFile, saveFile);
        log.info("file:{" + downloadFile + "} is download successful");
    }

    /**
     * 下载文件
     *
     * @param directory    下载目录
     * @param downloadFile 下载的文件名
     * @return 字节数组
     * @throws SftpException
     * @throws IOException
     * @throws Exception
     */
    public byte[] download(String directory, String downloadFile) throws SftpException, IOException {
        if (directory != null && !"".equals(directory)) {
            sftp.cd(directory);
        }
        InputStream is = sftp.get(downloadFile);
        byte[] fileData = IOUtils.toByteArray(is);
        log.info("file:{" + downloadFile + "} is download successful");
        return fileData;
    }

    /**
     * 下载文件流
     *
     * @param directory    下载目录
     * @param downloadFile 下载的文件名
     * @return 字节流
     * @throws SftpException
     * @throws IOException
     * @throws Exception
     */
    public InputStream downloadStream(String directory, String downloadFile) throws SftpException, IOException {
        if (directory != null && !"".equals(directory)) {
            sftp.cd(directory);
        }
        InputStream is = sftp.get(downloadFile);
        return is;
    }

    public InputStream downloadStream(String downloadFile) throws SftpException, IOException {
        InputStream is = sftp.get(downloadFile);
        return is;
    }

    public boolean createFile(InputStream inputStream, String filePath) {
        try {
            sftp.put(inputStream, filePath);
            log.info("创建文件完成");
            return true;
        } catch (Exception e) {
            log.info("创建文件失败");
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除文件
     *
     * @param directory  要删除文件所在目录
     * @param deleteFile 要删除的文件
     * @throws SftpException
     * @throws Exception
     */
    public void delete(String directory, String deleteFile) throws SftpException {
        sftp.cd(directory);
        sftp.rm(deleteFile);
    }

    public void deleteFile(String deleteFile) {
        try {
            sftp.rm(deleteFile);
            sftp.ls(sftp.pwd());
        } catch (SftpException e) {
            e.printStackTrace();
        }
    }

    /**
     * 列出目录下的文件
     *
     * @param directory 要列出的目录
     * @return
     * @throws SftpException
     */
    public Vector<LsEntry> listFiles(String directory) throws SftpException {
        return sftp.ls(directory);
    }


    public static Map<String, String> zipPaths(String path) {
        //因为这里我的zip文件是存放在707的子目录下面的，所以在这里做了遍历
        File file = new File(path + "\\");
        String fileName = null;
        String filePath = null;
        //在这里声明一个Map，将遍历得到的zip文件的名字作为key，zip文件的路径作为value。
        Map<String, String> fileMap = new HashMap<String, String>();
        if (file.isDirectory()) {
            for (File files : file.listFiles()) {
                if (files.isDirectory()) {
                    for (File f : files.listFiles()) {
                        if (f.isDirectory()) {
                            for (File ff : f.listFiles()) {
                                fileName = ff.getName();
                                filePath = ff.getPath();
                                fileMap.put(fileName, filePath);
                            }
                        } else {
                            fileName = f.getName();
                            filePath = f.getPath();
                            fileMap.put(fileName, filePath);
                        }
                    }
                } else {
                    fileName = files.getName();
                    filePath = files.getPath();
                    fileMap.put(fileName, filePath);
                }
            }
        }
        //最后返回一个map给调用者。
        return fileMap;
    }


    /**
     * 解压  zipPath 压缩的zip文件夹，unZipPath 文件夹
     *
     * @param zipPath
     * @param unZipPath
     * @return
     * @throws Exception
     */
    public String unZip(String zipPath, String unZipPath) throws Exception {
        String zipFileName = null;
        String ZipFilePath = null;
        String outputDirectory = null;
        //调用上面zipPaths方法，拿到map。
        Map<String, String> fileMap = zipPaths(zipPath);
        for (Map.Entry<String, String> entry : fileMap.entrySet()) {
            //压缩文件的名字
            zipFileName = entry.getKey();
            //压缩文件的路径
            ZipFilePath = entry.getValue();
            File file = new File(ZipFilePath);
            //得到zip文件的父文件夹的名字
            String parentFolder = file.getParent().substring(file.getParent().lastIndexOf("\\"));
            //这个是存放解压之后的文件的路径
            outputDirectory = unZipPath + parentFolder;
            org.apache.tools.zip.ZipFile zipFile = new org.apache.tools.zip.ZipFile(ZipFilePath);
            try {
                Enumeration<?> e = zipFile.getEntries();
                org.apache.tools.zip.ZipEntry zipEntry = null;
                createDirectory(outputDirectory, "");
                while (e.hasMoreElements()) {
                    zipEntry = (org.apache.tools.zip.ZipEntry) e.nextElement();
                    System.out.println("解压：" + zipEntry.getName());
                    if (zipEntry.isDirectory()) {
                        String name = zipEntry.getName();
                        name = name.substring(0, name.length() - 1);
                        File f = new File(outputDirectory + File.separator + name);
                        f.mkdir();
                        System.out.println("创建目录：" + outputDirectory + File.separator + name);
                    } else {
                        String fileName = zipEntry.getName();
                        fileName = fileName.replace('\\', '/');
                        if (fileName.indexOf("/") != -1) {
                            createDirectory(outputDirectory, fileName.substring(0,
                                    fileName.lastIndexOf("/")));
                            fileName = fileName.substring(
                                    fileName.lastIndexOf("/") + 1,
                                    fileName.length());
                        }
                        File f = new File(outputDirectory + File.separator
                                + zipEntry.getName());
                        f.createNewFile();
                        InputStream in = zipFile.getInputStream(zipEntry);
                        FileOutputStream out = new FileOutputStream(f);
                        byte[] by = new byte[1024];
                        int c;
                        while ((c = in.read(by)) != -1) {
                            out.write(by, 0, c);
                        }
                        in.close();
                        out.close();
                    }
                }
                System.out.println("解压 [" + zipFileName + "] 完成！");
            } catch (Exception ex) {
                System.out.println(ex.getMessage());
            } finally {
                zipFile.close();
            }
        }
        return outputDirectory;
    }

    private static void createDirectory(String directory, String subDirectory) {
        String[] dir;
        File fl = new File(directory);
        try {
            if ("".equals(subDirectory) && fl.exists() != true) {
                fl.mkdir();
            } else if (!"".equals(subDirectory)) {
                dir = subDirectory.replace('\\', '/').split("/");
                for (int i = 0; i < dir.length; i++) {
                    File subFile = new File(directory + File.separator + dir[i]);
                    if (subFile.exists() == false) {
                        subFile.mkdir();
                    }
                    directory += File.separator + dir[i];
                }
            }
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

    /**
     * 连接SFTP 获取文件
     *
     * @param pagePath
     * @param pageName
     * @return
     * @throws Exception
     */
    public InputStream getFileFromSFTP(String pagePath, String pageName) throws Exception {
        InputStream in = null;
        login();
        try {
            byte[] bytes = download(pagePath, pageName);
            in = new ByteArrayInputStream(bytes);
        } catch (Exception e) {
            logout();
            throw new Exception("获取文件失败");
        }
        logout();
        return in;
    }

    private void mkdir(String hostDir) throws SftpException {
        sftp.mkdir(hostDir);
    }

    public Map<String, String> getFilesFromSFTP() {
        login();
        Map<String, String> map = new HashMap<>();
        try {
            sftp.cd(hostDir);
            Vector<String> files = sftp.ls("*");
            getFileList(files, map);
        } catch (Exception e) {
            logout();
            e.printStackTrace();
            throw new Exception("获取文件失败");
        } finally {
            logout();
            return map;
        }
    }

    public void getFileList(Vector<String> files, Map<String, String> map) throws Exception {
        for (int i = 0; i < files.size(); i++) {
            Object obj = files.elementAt(i);
            if (obj instanceof com.jcraft.jsch.ChannelSftp.LsEntry) {
                LsEntry entry = (LsEntry) obj;
                if (!entry.getAttrs().isDir()) {
                    log.info("路径:" + sftp.pwd() + "---文件：" + entry.getFilename());
                    InputStream inputStream = downloadStream(entry.getFilename());
                    String result = new BufferedReader(new InputStreamReader(inputStream)).lines().collect(Collectors.joining(System.lineSeparator()));
                    map.put(sftp.pwd() + "/" + entry.getFilename(), result);
                }
                if (entry.getAttrs().isDir()) {
                    if (!".".equals(entry.getFilename()) && !"..".equals(entry.getFilename())) {
                        sftp.cd(entry.getFilename());
                        if (CollectionUtils.isEmpty(sftp.ls("*"))) {
                            log.info("路径:" + sftp.pwd() + "---文件夹：" + entry.getFilename());
                            sftp.cd("..");
                        } else {
                            log.info("路径:" + sftp.pwd() + "---文件夹：" + entry.getFilename());
                            getFileList(sftp.ls("*"), map);
                        }
                    }
                }
            }
            if (i == files.size() - 1) {
                sftp.cd("..");
            }
        }
    }

    public void isExist(String path, String name) throws Exception {
        try {
            sftp.cd(path);
            SftpATTRS stat = sftp.stat(name);
        } catch (Exception e) {
            throw new Exception("文件不存在");
        }

    }

    public static void main(String[] args) throws Exception {

        String username = "sftp";
        String password = "2020@newImage";
        String host = "49.232.18.205";
        int port = 22;
        String hostDir = "/upload";
        SFTPUtil sftpUtil = new SFTPUtil(username, password, host, port, hostDir);
        sftpUtil.login();
//        sftpUtil.isExist("/nfs/image/"+"a","ca30e214be8243eea7de9be8f015d807.jpg");
//        String s = "F:/新华/222/3/4/";
//        String[] dirs = s.split("/");
//        File file = new File("F:/新华/222/3/4/44");
//        if (!file.exists()) {
//            file.mkdirs();
//        }
        String newPath = "/upload/11";
        File file = new File("/upload/11");
        if (!file.exists()) {
            file.mkdirs();
        }
        String[] dirs = newPath.split("/");
        String tempPath = "";
        int index = 0;
//        mkdirDir(dirs, tempPath, dirs.length, index);
//        createDirectory("/upload/11","");
//      sftpUtil.upload("/upload/11/22","F:\\新华\\11.jpg");
//        sftpUtil.mkdirDir(dirs, tempPath, dirs.length, index);
//        sftpUtil.download("/nfs/image/applet/img/2021/05/21/idimg", "4d1ad761b55847c4a023c4ffed422fc5.jpg", "F:/新华/222/3/4/44");
        sftpUtil.logout();
    }

    /**
     * 递归根据路径创建文件夹--运用ThreadLocal方式
     *
     * @param path     目的路径
     * @return
     */
    public void mkdirDirs(String path) {
        Scope scope = threadLocal.get();
        // tempPath 拼接路径 第一次调用给默认值 "" 即可
        // index    数组下标 第一次调用给默认值 0 即可
        if (ObjectUtils.isEmpty(scope)) {
            scope = new Scope("", 0);
            threadLocal.set(scope);
        }
        String tempPath = scope.getTempPath();
        int index = scope.getIndex();
        String[] dirs = path.split("/");
        int length = dirs.length;

        // 以"/a/b/c/d"为例按"/"分隔后,第0位是"";顾下标从1开始
        index++;
        if (index < length) {
            // 目录不存在，则创建文件夹
            tempPath += "/" + dirs[index];
        }
        try {
            log.info("检测目录[" + tempPath + "]");
            sftp.cd(tempPath);
            if (index < length) {
                threadLocal.set(new Scope(tempPath, index));
                mkdirDirs(path);
            }
        } catch (SftpException ex) {
            log.warn("创建目录[" + tempPath + "]");
            try {
                sftp.mkdir(tempPath);
                sftp.cd(tempPath);
            } catch (SftpException e) {
                e.printStackTrace();
                log.error("创建目录[" + tempPath + "]失败,异常信息[" + e.getMessage() + "]");

            }
            log.info("进入目录[" + tempPath + "]");
            threadLocal.set(new Scope(tempPath, index));
            mkdirDirs(path);
        }
    }
    /**
     * 递归根据路径创建文件夹
     *
     * @param dirs     根据 / 分隔后的数组文件夹名称
     * @param tempPath 拼接路径
     * @param length   文件夹的格式
     * @param index    数组下标
     * @return
     */
    public void mkdirDir(String[] dirs, String tempPath, int length, int index) {
        // 以"/a/b/c/d"为例按"/"分隔后,第0位是"";顾下标从1开始
        index++;
        if (index < length) {
            // 目录不存在，则创建文件夹
            tempPath += "/" + dirs[index];
        }
        try {
            log.info("检测目录[" + tempPath + "]");
            sftp.cd(tempPath);
            if (index < length) {
                mkdirDir(dirs, tempPath, length, index);
            }
        } catch (SftpException ex) {
            log.warn("创建目录[" + tempPath + "]");
            try {
                sftp.mkdir(tempPath);
                sftp.cd(tempPath);
            } catch (SftpException e) {
                e.printStackTrace();
                log.error("创建目录[" + tempPath + "]失败,异常信息[" + e.getMessage() + "]");

            }
            log.info("进入目录[" + tempPath + "]");
            mkdirDir(dirs, tempPath, length, index);
        }
    }
}

class Scope {
    private String tempPath;
    private int index;

    public Scope(String tempPath, int index) {
        this.tempPath = tempPath;
        this.index = index;
    }

    public Scope() {
    }

    public String getTempPath() {
        return tempPath;
    }

    public void setTempPath(String tempPath) {
        this.tempPath = tempPath;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    @Override
    public String toString() {
        return "Scope{" +
                "tempPath='" + tempPath + '\'' +
                ", index=" + index +
                '}';
    }
}